# @file    : custom_audio_pygame
# @time    : 2024/4/25
# @author  : yongpeng.yao
# @desc    :

__all__ = ('SoundLoaderPygame', 'get_devices')

from typing import Tuple

import pygame
import pygame._sdl2.audio as sdl2_audio
from kivy import Logger
from kivy.clock import Clock
from kivy.core.audio import Sound
from kivy.resources import resource_find
from pygame import mixer

from common import get_music_length
from common.local_conf import get_conf_device

mixer.pre_init(44100, -16, 2, 1024)
mixer.init(devicename=get_conf_device())
mixer.set_num_channels(32)


class SoundLoaderPygame:
    """Load a sound, using the best loader for the given file type.
    """
    _classes = []

    @staticmethod
    def register(classobj):
        """Register a new class to load the sound."""
        Logger.debug('Audio: register %s' % classobj.__name__)
        SoundLoaderPygame._classes.append(classobj)

    @staticmethod
    def load(filename):
        """Load a sound, and return a Sound() instance."""
        rfn = resource_find(filename)
        if rfn is not None:
            filename = rfn
        ext = filename.split('.')[-1].lower()
        if '?' in ext:
            ext = ext.split('?')[0]
        for classobj in SoundLoaderPygame._classes:
            if ext in classobj.extensions():
                return classobj(source=filename)
        Logger.warning('Audio: Unable to find a loader for <%s>' % filename)
        return None


class SoundPygameDev(Sound):
    # XXX we don't set __slots__ here, to automatically add
    # a dictionary. We need that to be able to use weakref for
    # SoundPygame object. Otherwise, it failed with:
    # TypeError: cannot create weak reference to 'SoundPygame' object
    # We use our clock in play() method.
    # __slots__ = ('_data', '_channel')
    _check_play_ev = None

    @staticmethod
    def extensions():
        return 'wav', 'ogg', 'mp3', 'flac'

    def __init__(self, **kwargs):
        self.start_time = None
        self.__mixer = None  # pygame.mixer
        self._player = None  # pygame.mixer.music
        self.play_start_pos = 0.0
        super(SoundPygameDev, self).__init__(**kwargs)

    def _check_play(self, dt):
        if self._player is None:
            return False
        if self._player.get_busy():
            return
        if self.loop:
            def do_loop():
                self.play()

            Clock.schedule_once(lambda dt_: do_loop())
        else:
            self.stop()
        return False

    def on_source(self, instance, filename):
        self.unload()
        if filename is None:
            return

    def play(self):
        self.load()
        if not self._player:
            return
        if not self.__mixer:
            return
        self._player.set_volume(self.volume)
        self._player.play(start=self.play_start_pos)
        self.start_time = Clock.time()
        # schedule event to check if the sound is still playing or not
        self._check_play_ev = Clock.schedule_interval(self._check_play, 0.1)
        super(SoundPygameDev, self).play()

    def stop(self):
        if not self._player:
            return
        if not self.__mixer:
            return
        try:
            self._player.stop()
            self._player.unload()
        except pygame.error:
            pass
        # ensure we don't have anymore the callback
        if self._check_play_ev is not None:
            self._check_play_ev.cancel()
            self._check_play_ev = None
        self._player = None
        super(SoundPygameDev, self).stop()

    def load(self):
        self.unload()
        if self.source is None:
            return
        if self.__mixer is None:
            self.__mixer = mixer  # init mixer
        self._player = self.__mixer.music
        self._player.unload()
        try:
            self._player.load(self.source)
        except pygame.error:
            pass

    def unload(self):
        self.stop()
        if self._player:
            self._player.unload()
            self._player = None
        if self.__mixer:
            self.__mixer = None

    def seek(self, position):
        self.play_start_pos = position
        if self._player is not None and self._player.get_busy():
            # self._player.set_pos(position)  # 该方法不起作用
            self._player.stop()
            self.play()

    def get_pos(self):
        if self._player is not None and self._player.get_pos() > 0:
            return self._player.get_pos() / 1000 + self.play_start_pos

        return 0

    def on_volume(self, instance, volume):
        if self._player is not None:
            try:
                self._player.set_volume(volume)
            except pygame.error:
                pass

    def _get_length(self):
        try:
            return get_music_length(self.source)
        except pygame.error:
            pass

        return super(SoundPygameDev, self)._get_length()

    def set_pause(self):
        if self._player is not None:
            try:
                self._player.pause()
            except pygame.error:
                pass

    def set_unpause(self):
        if self._player is not None:
            try:
                self._player.unpause()
            except pygame.error:
                pass


SoundLoaderPygame.register(SoundPygameDev)


def get_devices(capture_devices: bool = False) -> Tuple[str, ...]:
    init_by_me = not pygame.mixer.get_init()
    if init_by_me:
        pygame.mixer.init()
    devices = tuple(sdl2_audio.get_audio_device_names(capture_devices))
    if init_by_me:
        pygame.mixer.quit()
    return devices
